/*
 *  Multi UART console serial I/O.
 *
 *  TO DO: Add DMA input/output
 */

#include <stdio.h>
#include <fcntl.h>
#include <termios.h>
#include <bsp.h>
#include <malloc.h>

#include <rtems/bspIo.h>
#include <rtems/console.h>
#include <rtems/libio.h>
#include <rtems/termiostypes.h>

#define UART_INTC0_IRQ_VECTOR(x) (64+13+(x))

#define MCF5282_UART_USR_ERROR ( MCF5282_UART_USR_RB | \
                                 MCF5282_UART_USR_FE | \
                                 MCF5282_UART_USR_PE | \
                                 MCF5282_UART_USR_OE )

static ssize_t IntUartPollWrite(int minor, const char *buf, size_t len);
static ssize_t IntUartInterruptWrite (int minor, const char *buf, size_t len);

static void _BSP_null_char( char c )
{
  rtems_interrupt_level level;

  rtems_interrupt_disable(level);
  while ( (MCF5282_UART_USR(CONSOLE_PORT) & MCF5282_UART_USR_TXRDY) == 0 )
    continue;

  MCF5282_UART_UTB(CONSOLE_PORT) = c;
  while ( (MCF5282_UART_USR(CONSOLE_PORT) & MCF5282_UART_USR_TXRDY) == 0 )
    continue;

  rtems_interrupt_enable(level);
}
BSP_output_char_function_type     BSP_output_char = _BSP_null_char;
BSP_polling_getchar_function_type BSP_poll_char = NULL;

#define MAX_UART_INFO     3
#define RX_BUFFER_SIZE    512

struct IntUartInfoStruct
{
  int           iomode;
  volatile int  uimr;
  int           baud;
  int           databits;
  int           parity;
  int           stopbits;
  int           hwflow;
  int           rx_in;
  int           rx_out;
  char          rx_buffer[RX_BUFFER_SIZE];
  void         *ttyp;
};

struct IntUartInfoStruct IntUartInfo[MAX_UART_INFO];

/*
 * Function : IntUartSet
 *
 * Description : This updates the hardware UART settings.
 */
static void IntUartSet(
  int minor,
  int baud,
  int databits,
  int parity,
  int stopbits,
  int hwflow
)
{
  int                       divisor;
  uint32_t                  clock_speed;
  uint8_t                   umr1 = 0;
  uint8_t                   umr2 = 0;
  struct IntUartInfoStruct *info = &IntUartInfo[minor];
  rtems_interrupt_level     level;

  rtems_interrupt_disable(level);

  /* disable interrupts, clear RTS line, and disable the UARTS */
  MCF5282_UART_UIMR(minor) = 0;
  MCF5282_UART_UOP0(minor) = 1;
  MCF5282_UART_UCR(minor) =
    (MCF5282_UART_UCR_TX_DISABLED | MCF5282_UART_UCR_RX_DISABLED);

  /* save the current values */
  info->uimr     = 0;
  info->baud     = baud;
  info->databits = databits;
  info->parity   = parity;
  info->stopbits = stopbits;
  info->hwflow   = hwflow;

  clock_speed = get_CPU_clock_speed();
  /* determine the baud divisor value */
  divisor = (clock_speed / ( 32 * baud ));
  if ( divisor < 2 )
    divisor = 2;

  /* check to see if doing hardware flow control */
  if ( hwflow ) {
    /* set hardware flow options */
    umr1 |= MCF5282_UART_UMR1_RXRTS;
    umr2 |= MCF5282_UART_UMR2_TXCTS;
  }

  /* determine the new umr values */
  umr1 |= (parity | databits);
  umr2 |= (stopbits);

  /* reset the uart */
  MCF5282_UART_UCR(minor) = MCF5282_UART_UCR_RESET_ERROR;
  MCF5282_UART_UCR(minor) = MCF5282_UART_UCR_RESET_RX;
  MCF5282_UART_UCR(minor) = MCF5282_UART_UCR_RESET_TX;

  /* reset the uart mode register and update values */
  MCF5282_UART_UCR(minor) = MCF5282_UART_UCR_RESET_MR;
  MCF5282_UART_UMR(minor) = umr1;
  MCF5282_UART_UMR(minor) = umr2;

  /* set the baud rate values */
  MCF5282_UART_UCSR(minor) =
    (MCF5282_UART_UCSR_RCS_SYS_CLK | MCF5282_UART_UCSR_TCS_SYS_CLK);
  MCF5282_UART_UBG1(minor) = (divisor & 0xff00) >> 8;
  MCF5282_UART_UBG2(minor) = (divisor & 0x00ff);

  /* enable the uart */
  MCF5282_UART_UCR(minor) =
    (MCF5282_UART_UCR_TX_ENABLED | MCF5282_UART_UCR_RX_ENABLED);

  /* check to see if interrupts need to be enabled */
  if ( info->iomode != TERMIOS_POLLED ) {
    /* enable rx interrupts */
    info->uimr |= MCF5282_UART_UIMR_FFULL;
    MCF5282_UART_UIMR(minor) = info->uimr;
  }

  /* check to see if doing hardware flow control */
  if ( hwflow ) {
    /* assert the RTS line */
    MCF5282_UART_UOP1(minor) = 1;
  }

  rtems_interrupt_enable(level);
}

/*
 * Function : IntUartSetAttributes
 *
 * Description : This provides the hardware-dependent portion of tcsetattr().
 * value and sets it. At the moment this just sets the baud rate.
 *
 * Note: The highest baudrate is 115200 as this stays within
 * an error of +/- 5% at 25MHz processor clock
 */
static int IntUartSetAttributes(
  int                   minor,
  const struct termios *t
)
{
  /* set default index values */
  int                         baud     = (int)19200;
  int                         databits = (int)MCF5282_UART_UMR1_BC_8;
  int                         parity   = (int)MCF5282_UART_UMR1_PM_NONE;
  int                         stopbits = (int)MCF5282_UART_UMR2_STOP_BITS_1;
  int                         hwflow   = (int)0;
  struct IntUartInfoStruct   *info     = &IntUartInfo[minor];

  /* check to see if input is valid */
  if ( t != (const struct termios *)0 ) {
    /* determine baud rate index */
    baud = rtems_termios_baud_to_number(t->c_ospeed);

    /* determine data bits */
    switch ( t->c_cflag & CSIZE ) {
      case CS5:
        databits = (int)MCF5282_UART_UMR1_BC_5;
        break;
      case CS6:
        databits = (int)MCF5282_UART_UMR1_BC_6;
        break;
      case CS7:
        databits = (int)MCF5282_UART_UMR1_BC_7;
        break;
      case CS8:
        databits = (int)MCF5282_UART_UMR1_BC_8;
        break;
    }

    /* determine if parity is enabled */
    if ( t->c_cflag & PARENB ) {
      if ( t->c_cflag & PARODD ) {
        /* odd parity */
        parity = (int)MCF5282_UART_UMR1_PM_ODD;
      } else {
        /* even parity */
        parity = (int)MCF5282_UART_UMR1_PM_EVEN;
      }
    }

    /* determine stop bits */
    if ( t->c_cflag & CSTOPB ) {
      /* two stop bits */
      stopbits = (int)MCF5282_UART_UMR2_STOP_BITS_2;
    }

    /* check to see if hardware flow control */
    if ( t->c_cflag & CRTSCTS ) {
      hwflow = 1;
    }
  }

  /* check to see if values have changed */
  if ( ( baud     != info->baud     ) ||
     ( databits != info->databits ) ||
     ( parity   != info->parity   ) ||
     ( stopbits != info->stopbits ) ||
     ( hwflow   != info->hwflow   ) ) {

    /* call function to set values */
    IntUartSet(minor, baud, databits, parity, stopbits, hwflow);
  }

  return RTEMS_SUCCESSFUL;
}

/*
 * Function : IntUartInterruptHandler
 *
 * Description : This is the interrupt handler for the internal uart. It
 * determines which channel caused the interrupt before queueing any received
 * chars and dequeueing chars waiting for transmission.
 */
static rtems_isr IntUartInterruptHandler(rtems_vector_number v)
{
  unsigned int                chan = v - UART_INTC0_IRQ_VECTOR(0);
  struct IntUartInfoStruct   *info = &IntUartInfo[chan];

  /* check to see if received data */
  if ( MCF5282_UART_UISR(chan) & MCF5282_UART_UISR_RXRDY ) {
    /* read data and put into the receive buffer */
    while ( MCF5282_UART_USR(chan) & MCF5282_UART_USR_RXRDY ) {

      if ( MCF5282_UART_USR(chan) & MCF5282_UART_USR_ERROR ) {
        /* clear the error */
        MCF5282_UART_UCR(chan) = MCF5282_UART_UCR_RESET_ERROR;
      }
      /* put data in rx buffer and check for errors */
      info->rx_buffer[info->rx_in] = MCF5282_UART_URB(chan);

      /* update buffer values */
      info->rx_in++;

      if ( info->rx_in >= RX_BUFFER_SIZE ) {
        info->rx_in = 0;
      }
    }

    /* Make sure the port has been opened */
    if ( info->ttyp ) {

      /* check to see if task driven */
      if ( info->iomode == TERMIOS_TASK_DRIVEN ) {
        /* notify rx task that rx buffer has data */
        rtems_termios_rxirq_occured(info->ttyp);
      } else {
        /* Push up the received data */
        rtems_termios_enqueue_raw_characters(
            info->ttyp, info->rx_buffer, info->rx_in);
        info->rx_in    = 0;
      }
    }
  }

  /* check to see if data needs to be transmitted */
  if ( ( info->uimr & MCF5282_UART_UIMR_TXRDY ) &&
     ( MCF5282_UART_UISR(chan) & MCF5282_UART_UISR_TXRDY ) )
  {

    /* disable tx interrupts */
    info->uimr &= ~MCF5282_UART_UIMR_TXRDY;
    MCF5282_UART_UIMR(chan) = info->uimr;

    /* tell upper level that character has been sent */
    if ( info->ttyp )
      rtems_termios_dequeue_characters(info->ttyp, 1);
  }
}

/*
 * Function : IntUartInitialize
 *
 * Description : This initialises the internal uart hardware for all
 * internal uarts. If the internal uart is to be interrupt driven then the
 * interrupt vectors are hooked.
 */
static void IntUartInitialize(void)
{
  unsigned int              chan;
  struct IntUartInfoStruct *info;
  rtems_isr_entry           old_handler;
  rtems_interrupt_level     level;

  for ( chan = 0; chan < MAX_UART_INFO; chan++ ) {
    info = &IntUartInfo[chan];

    info->ttyp     = NULL;
    info->rx_in    = 0;
    info->rx_out   = 0;
    info->baud     = -1;
    info->databits = -1;
    info->parity   = -1;
    info->stopbits = -1;
    info->hwflow   = -1;
    info->iomode   = TERMIOS_POLLED;

    MCF5282_UART_UACR(chan) = 0;
    MCF5282_UART_UIMR(chan) = 0;
    if ( info->iomode != TERMIOS_POLLED ) {
      rtems_interrupt_catch (IntUartInterruptHandler,
                   UART_INTC0_IRQ_VECTOR(chan),
                   &old_handler);
    }

    /* set uart default values */
    IntUartSetAttributes(chan, NULL);

    /* unmask interrupt */
    rtems_interrupt_disable(level);
    switch(chan) {
      case 0:
        MCF5282_INTC0_ICR13 = MCF5282_INTC_ICR_IL(UART0_IRQ_LEVEL) |
                              MCF5282_INTC_ICR_IP(UART0_IRQ_PRIORITY);
        MCF5282_INTC0_IMRL &= ~(MCF5282_INTC_IMRL_INT13 |
                              MCF5282_INTC_IMRL_MASKALL);
        break;

    case 1:
      MCF5282_INTC0_ICR14 = MCF5282_INTC_ICR_IL(UART1_IRQ_LEVEL) |
                            MCF5282_INTC_ICR_IP(UART1_IRQ_PRIORITY);
      MCF5282_INTC0_IMRL &= ~(MCF5282_INTC_IMRL_INT14 |
                            MCF5282_INTC_IMRL_MASKALL);
    break;

    case 2:
      MCF5282_INTC0_ICR15 = MCF5282_INTC_ICR_IL(UART2_IRQ_LEVEL) |
                            MCF5282_INTC_ICR_IP(UART2_IRQ_PRIORITY);
      MCF5282_INTC0_IMRL &= ~(MCF5282_INTC_IMRL_INT15 |
			    MCF5282_INTC_IMRL_MASKALL);
      break;
    }
    rtems_interrupt_enable(level);

  } /* of chan loop */


} /* IntUartInitialise */


/*
 * Function : IntUartInterruptWrite
 *
 * Description : This writes a single character to the appropriate uart
 * channel. This is either called during an interrupt or in the user's task
 * to initiate a transmit sequence. Calling this routine enables Tx
 * interrupts.
 */
static ssize_t IntUartInterruptWrite(
  int         minor,
  const char *buf,
  size_t      len
)
{
  if (len > 0) {
    /* write out character */
    MCF5282_UART_UTB(minor) = *buf;

    /* enable tx interrupt */
    IntUartInfo[minor].uimr |= MCF5282_UART_UIMR_TXRDY;
    MCF5282_UART_UIMR(minor) = IntUartInfo[minor].uimr;
  }

  return 0;
}

/*
 * Function : IntUartInterruptOpen
 *
 * Description : This enables interrupts when the tty is opened.
 */
static int IntUartInterruptOpen(
  int   major,
  int   minor,
  void *arg
)
{
  struct IntUartInfoStruct   *info = &IntUartInfo[minor];

  /* enable the uart */
  MCF5282_UART_UCR(minor) = (MCF5282_UART_UCR_TX_ENABLED |
                             MCF5282_UART_UCR_RX_ENABLED);

  /* check to see if interrupts need to be enabled */
  if ( info->iomode != TERMIOS_POLLED ) {
    /* enable rx interrupts */
    info->uimr |= MCF5282_UART_UIMR_FFULL;
    MCF5282_UART_UIMR(minor) = info->uimr;
  }

  /* check to see if doing hardware flow control */
  if ( info->hwflow ) {
    /* assert the RTS line */
    MCF5282_UART_UOP1(minor) = 1;
  }

  return 0;
}

/*
 * Function : IntUartInterruptClose
 *
 * Description : This disables interrupts when the tty is closed.
 */
static int IntUartInterruptClose(
  int   major,
  int   minor,
  void *arg
)
{
  struct IntUartInfoStruct   *info = &IntUartInfo[minor];

  /* disable the interrupts and the uart */
  MCF5282_UART_UIMR(minor) = 0;
  MCF5282_UART_UCR(minor) =
    (MCF5282_UART_UCR_TX_DISABLED | MCF5282_UART_UCR_RX_DISABLED);

  /* reset values */
  info->ttyp     = NULL;
  info->uimr       = 0;
  info->rx_in    = 0;
  info->rx_out   = 0;

  return 0;
}

/*
 * Function : IntUartTaskRead
 *
 * Description : This reads all available characters from the internal uart
 * and places them into the termios buffer.  The rx interrupts will be
 * re-enabled after all data has been read.
 */
static int IntUartTaskRead(int minor)
{
  char                        buffer[RX_BUFFER_SIZE];
  int                         count;
  int                         rx_in;
  int                         index = 0;
  struct IntUartInfoStruct   *info  = &IntUartInfo[minor];

  /* determine number of values to copy out */
  rx_in = info->rx_in;
  if ( info->rx_out <= rx_in ) {
    count = rx_in - info->rx_out;
  } else {
    count = (RX_BUFFER_SIZE - info->rx_out) + rx_in;
  }

  /* copy data into local buffer from rx buffer */
  while ( ( index < count ) && ( index < RX_BUFFER_SIZE ) ) {
    /* copy data byte */
    buffer[index] = info->rx_buffer[info->rx_out];
    index++;

    /* increment rx buffer values */
    info->rx_out++;
    if ( info->rx_out >= RX_BUFFER_SIZE ) {
      info->rx_out = 0;
    }
  }

  /* check to see if buffer is not empty */
  if ( count > 0 ) {
    /* set characters into termios buffer  */
    rtems_termios_enqueue_raw_characters(info->ttyp, buffer, count);
  }

  return EOF;
}



/*
 * Function : IntUartPollRead
 *
 * Description : This reads a character from the internal uart. It returns
 * to the caller without blocking if not character is waiting.
 */
static int IntUartPollRead(int minor)
{
  if ( (MCF5282_UART_USR(minor) & MCF5282_UART_USR_RXRDY) == 0 )
    return-1;

  return MCF5282_UART_URB(minor);
}


/*
 * Function : IntUartPollWrite
 *
 * Description : This writes out each character in the buffer to the
 * appropriate internal uart channel waiting till each one is sucessfully
 * transmitted.
 */
static ssize_t IntUartPollWrite(
  int         minor,
  const char *buf,
  size_t      len
)
{
  size_t retval = len;
  /* loop over buffer */

  while ( len-- ) {
    /* block until we can transmit */
    while ( (MCF5282_UART_USR(minor) & MCF5282_UART_USR_TXRDY) == 0 )
      continue;
    /* transmit data byte */
    MCF5282_UART_UTB(minor) = *buf++;
  }
  return retval;
}

/*
 * Function : console_initialize
 *
 * Description : This initialises termios, both sets of uart hardware before
 * registering /dev/tty devices for each channel and the system /dev/console.
 */
rtems_device_driver console_initialize(
  rtems_device_major_number  major,
  rtems_device_minor_number  minor,
  void                      *arg
)
{
  rtems_status_code status;

  /* Set up TERMIOS */
  rtems_termios_initialize ();

  /* set io modes for the different channels and initialize device */
  IntUartInfo[minor].iomode = TERMIOS_IRQ_DRIVEN;
  IntUartInitialize();

  /* Register the console port */
  status = rtems_io_register_name ("/dev/console", major, CONSOLE_PORT);
  if ( status != RTEMS_SUCCESSFUL ) {
    rtems_fatal_error_occurred (status);
  }

  /* Register the other port */
  if ( CONSOLE_PORT != 0 ) {
    status = rtems_io_register_name ("/dev/tty00", major, 0);
    if ( status != RTEMS_SUCCESSFUL ) {
      rtems_fatal_error_occurred (status);
    }
  }
  if ( CONSOLE_PORT != 1 ) {
    status = rtems_io_register_name ("/dev/tty01", major, 1);
    if ( status != RTEMS_SUCCESSFUL ) {
      rtems_fatal_error_occurred (status);
    }
  }

  return RTEMS_SUCCESSFUL;
}

/*
 * Function : console_open
 *
 * Description : This actually opens the device depending on the minor
 * number set during initialisation. The device specific access routines are
 * passed to termios when the devices is opened depending on whether it is
 * polled or not.
 */
rtems_device_driver console_open(
  rtems_device_major_number major,
  rtems_device_minor_number minor,
  void  * arg
)
{
  rtems_status_code               status = RTEMS_INVALID_NUMBER;
  rtems_libio_open_close_args_t  *args   = (rtems_libio_open_close_args_t *)arg;
  struct IntUartInfoStruct       *info;

  static const rtems_termios_callbacks IntUartPollCallbacks = {
    NULL,                 /* firstOpen */
    NULL,                 /* lastClose */
    IntUartPollRead,      /* pollRead */
    IntUartPollWrite,     /* write */
    IntUartSetAttributes, /* setAttributes */
    NULL,                 /* stopRemoteTx */
    NULL,                 /* startRemoteTx */
    TERMIOS_POLLED        /* mode */
  };
  static const rtems_termios_callbacks IntUartIntrCallbacks = {
    IntUartInterruptOpen,  /* firstOpen */
    IntUartInterruptClose, /* lastClose */
    NULL,                  /* pollRead */
    IntUartInterruptWrite, /* write */
    IntUartSetAttributes,  /* setAttributes */
    NULL,                  /* stopRemoteTx */
    NULL,                  /* startRemoteTx */
    TERMIOS_IRQ_DRIVEN     /* mode */
  };

  static const rtems_termios_callbacks IntUartTaskCallbacks = {
    IntUartInterruptOpen,  /* firstOpen */
    IntUartInterruptClose, /* lastClose */
    IntUartTaskRead,       /* pollRead */
    IntUartInterruptWrite, /* write */
    IntUartSetAttributes,  /* setAttributes */
    NULL,                  /* stopRemoteTx */
    NULL,                  /* startRemoteTx */
    TERMIOS_TASK_DRIVEN    /* mode */
  };

  /* open the port depending on the minor device number */
  if ( ( minor >= 0 ) && ( minor < MAX_UART_INFO ) ) {
    info = &IntUartInfo[minor];
    switch ( info->iomode ) {
      case TERMIOS_POLLED:
        status = rtems_termios_open(major, minor, arg, &IntUartPollCallbacks);
        break;
      case TERMIOS_IRQ_DRIVEN:
        status = rtems_termios_open(major, minor, arg, &IntUartIntrCallbacks);
        info->ttyp = args->iop->data1;
        break;
      case TERMIOS_TASK_DRIVEN:
        status = rtems_termios_open(major, minor, arg, &IntUartTaskCallbacks);
        info->ttyp = args->iop->data1;
        break;
    }
  }

  return status;
}

/*
 * Function : console_close
 *
 * Description : This closes the device via termios
 */
rtems_device_driver console_close(
  rtems_device_major_number major,
  rtems_device_minor_number minor,
  void   * arg
)
{
  return rtems_termios_close(arg);
}

/*
 * Function : console_read
 *
 * Description : Read from the device via termios
 */
rtems_device_driver console_read(
  rtems_device_major_number  major,
  rtems_device_minor_number  minor,
  void                      *arg
)
{
  return rtems_termios_read(arg);
}

/*
 * Function : console_write
 *
 * Description : Write to the device via termios
 */
rtems_device_driver console_write(
  rtems_device_major_number  major,
  rtems_device_minor_number  minor,
  void                      *arg
)
{
  return rtems_termios_write(arg);
}

/*
 * Function : console_ioctl
 *
 * Description : Pass the IOCtl call to termios
 */
rtems_device_driver console_control(
  rtems_device_major_number  major,
  rtems_device_minor_number  minor,
  void                      *arg
)
{
  return rtems_termios_ioctl(arg);
}
